using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Magiq.Tests.Support;
using Magiq.Tests.TestModel;
using Magiq.Update;
using NUnit.Framework;

namespace Magiq.Tests
{
    [TestFixture]
    public abstract class BaseMagiqProviderCompositeKeysTest<TParent,TChild> where TParent : class,ICompositePkParent<TChild>,new() where TChild : class, ICompositePkChild, new()
    {
        protected IMagiqTestConfigurator Configurator { get; private set; }
        protected abstract IMagiqTestConfigurator GetConfigurator();

        protected BaseMagiqProviderCompositeKeysTest()
        {
            Configurator = GetConfigurator();
        }

        #region Setup/Teardown

        [SetUp]
        public void SetUp()
        {
            Configurator.SetUp();
            domain = new CompositePkTestDomain<TParent, TChild>();
            parents = null;
            Configurator.Persist(domain.Children);
            Configurator.Reload();
            children = Configurator.GetQueryable<TChild>();
        }

        [TearDown]
        public void TearDown()
        {
            Configurator.TearDown();
        }

        #endregion

        private IQueryable<TParent> Parents
        {
            get { return parents ?? (parents = Configurator.GetQueryable<TParent>()); }
        }

        private IQueryable<TChild> children;
        private CompositePkTestDomain<TParent, TChild> domain;
        private IQueryable<TParent> parents;

        private static void TestDelete<T>(Expression<Func<T, bool>> where, IQueryable<T> items) where T : class
        {
            var count = items.Where(where).Count();
            items.Where(where).Delete().Should(Be.EqualTo(count));
            items.Where(where).Count().Should(Be.EqualTo(0));
        }

        private static void TestUpdate<T>(Expression<Func<T, bool>> where, Func<IQueryable<T>, IUpdate<T>> set,
                                          IQueryable<T> items)
        {
            TestUpdate(where, x => x.Count(), set, items);
        }

        private static void TestUpdate<T>(Expression<Func<T, bool>> where, Func<IEnumerable<T>, int> getCount,
                                          Func<IQueryable<T>, IUpdate<T>> set, IQueryable<T> items)
        {
            var count = getCount(items.Where(where));
            set(items.Where(where)).Update().Should(Be.EqualTo(count));
        }

        private static void TestInsert<T>(Expression<Func<T, bool>> where, Expression<Func<T, T>> insert,
                                          IQueryable<T> items) where T : class, new()
        {
            var totalCount = items.Count();
            var count = items.Where(where).Count();
            items.Insert(items.Where(where).Select(insert));
            items.Count().Should(Be.EqualTo(totalCount + count));
        }

        [Test]
        public void ShouldDeleteEntityWithCompositePrimaryKey()
        {
            TestDelete(x => true, children);
        }

        [Test]
        public void ShouldDeleteWithWhereContainingAnAssociationWithCompositePrimaryKey()
        {
            TestDelete(x => x.Child.Prop == "test1", Parents);
        }

        [Test]
        public void ShouldInsertAssociationWithCompositePrimaryKey()
        {
            TestInsert(x => true, x => new TParent
                                           {
                                               Prop = "bbla",
                                               Child = x.Child
                                           }, Parents);
        }

        [Test]
        public void ShouldInsertEntityWithCompositePrimaryKey()
        {
            TestInsert(x => true, x => new TChild
                                           {
                                               Id1 = x.Id1 + 1000,
                                               Id2 = x.Id1,
                                               Prop = "lala"
                                           }, children);
        }

        [Test]
        public void ShouldInsertWithWhereContainingAnAssociationWithCompositePrimaryKey()
        {
            TestInsert(x => x.Child != null && x.Child.Prop == "test1", x => new TParent
                                                              {
                                                                  Prop = "lala"
                                                              }, Parents);
        }

        [Test]
        public void ShouldUpdateAssociationEntityWithCompositePrimaryKey()
        {
            TestUpdate(x => true, x => x.Where(u => u.Child != null).Select(u => u.Child).Count(),
                       s => s.Set(x => x.Child.Prop, "lele"), Parents);
        }

        [Test]
        public void ShouldUpdateAssociationWithCompositePrimaryKey()
        {
            TestUpdate(x => true,
                       s => s.Set(x => x.Child, x => x.Child),
                       Parents);
        }

        [Test]
        public void ShouldUpdateEntityWithCompositePrimaryKey()
        {
            TestUpdate(x => true, s => s.Set(x => x.Prop, "lala"), children);
        }

        [Test]
        public void ShouldUpdateWithWhereContainingAnAssociationWithCompositePrimaryKey()
        {
            TestUpdate(x => x.Child.Prop == "test1", s => s.Set(x => x.Prop, "lala"), Parents);
        }
    }
}